#include "d3d11render.h"


static unsigned int get_feature_level_index( const D3D_FEATURE_LEVEL& level )
{
	if( level == D3D_FEATURE_LEVEL_11_0 )
		return 7;
	else if( level == D3D_FEATURE_LEVEL_10_1 )
		return 6;
	else if( level == D3D_FEATURE_LEVEL_10_0 )
		return 5;
	else if( level == D3D_FEATURE_LEVEL_9_3 )
		return 4;
	else if( level == D3D_FEATURE_LEVEL_9_2 )
		return 3;
	else if( level == D3D_FEATURE_LEVEL_9_1 )
		return 2;
	else
		return 0;
}





//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=//
//								PRIMARY RENDERER CONSTRUCTION AND INIT								  //
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=//


dh_renderer::dh_renderer()
{
	pDevice			= NULL;
	pSwapChain		= NULL;
	pDeviceContext  = NULL;
	pRTV			= NULL;
	pDSV			= NULL;
	
	featureLevelIndex		= 0;
}


dh_renderer::~dh_renderer()
{
	RELEASE_UNKNOWN( pDevice );
	RELEASE_UNKNOWN( pSwapChain );
	RELEASE_UNKNOWN( pDeviceContext );
	RELEASE_UNKNOWN( pRTV );
	RELEASE_UNKNOWN( pDSV );
	
	int nElements = vVertexBuffers.size();
	int i = 0;
	if( nElements > 0 )
	{
		for( i = 0; i < nElements; ++i )
			RELEASE_UNKNOWN( vVertexBuffers[i] );
	}
	vVertexBuffers.clear();
	
	nElements = vIndexBuffers.size();
	if( nElements > 0 )
	{
		for( i = 0; i < nElements; ++i )
			RELEASE_UNKNOWN( vIndexBuffers[i] );
	}
	vIndexBuffers.clear();
	

	nElements = vVertexFormats.size();
	if( nElements > 0 )
	{
		for( i = 0; i < nElements; ++i )
			RELEASE_UNKNOWN( vVertexFormats[i] );
	}
	vVertexFormats.clear();

	nElements = vVertexShaders.size();
	for(i = 0; i < nElements; ++i)
		RELEASE_UNKNOWN(vVertexShaders[i]);
	vVertexShaders.clear();


	
	vVertexBufferSlots.clear();
	vIndexBufferSlots.clear();
	
	featureLevelIndex		= 0;
}



//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// InitializeRenderer
bool dh_renderer::InitializeRenderer( const dh_render_init& init )
{
	D3D_FEATURE_LEVEL featureLevels[] = 
	{
		D3D_FEATURE_LEVEL_11_0,
		D3D_FEATURE_LEVEL_10_1,
		D3D_FEATURE_LEVEL_10_0,
		D3D_FEATURE_LEVEL_9_3,
		D3D_FEATURE_LEVEL_9_2,
		D3D_FEATURE_LEVEL_9_1,
	};
	
	DXGI_SWAP_CHAIN_DESC scd;
	DXGI_MODE_DESC		 bufferDesc;
	memset( &scd, 0, sizeof( DXGI_SWAP_CHAIN_DESC ) );
	memset( &bufferDesc, 0, sizeof( DXGI_MODE_DESC ) );
	
	DXGI_SAMPLE_DESC	 multisampleDesc;
	multisampleDesc.Count			= 1;
	multisampleDesc.Quality			= 0;
	
	DXGI_USAGE			 bufferUsage;
	
	DXGI_RATIONAL		 refresh;
	refresh.Numerator    = init.refreshRate;
	refresh.Denominator  = 1;
	
	// Setup the back buffer descriptor //
	bufferDesc.Width				= init.displayWidth;
	bufferDesc.Height				= init.displayHeight;
	bufferDesc.RefreshRate			= refresh;
	bufferDesc.Format				= DXGI_FORMAT_R8G8B8A8_UNORM;
	bufferDesc.ScanlineOrdering		= DXGI_MODE_SCANLINE_ORDER_PROGRESSIVE;
	bufferDesc.Scaling				= DXGI_MODE_SCALING_CENTERED;
	
	// Setup the swap chain descriptor //
	scd.BufferDesc		= bufferDesc;
	scd.SampleDesc		= multisampleDesc;
//	scd.BufferUsage		= DXGI_USAGE_BACK_BUFFER;
	scd.BufferUsage		= DXGI_USAGE_RENDER_TARGET_OUTPUT;
	scd.BufferCount     = 2;
	scd.OutputWindow    = init.window;
	scd.Windowed		= (BOOL)init.windowed;
	scd.SwapEffect		= DXGI_SWAP_EFFECT_DISCARD;
	scd.Flags			= 0;
	
	unsigned int flags = D3D11_CREATE_DEVICE_SINGLETHREADED;


#ifdef _DEBUG
	flags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

	// Create the device //
	HRESULT hr;
	hr = D3D11CreateDeviceAndSwapChain( NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, flags, 
										featureLevels, 6, D3D11_SDK_VERSION, &scd,
										&pSwapChain, &pDevice, &curFeatureLevel, &pDeviceContext );

	if( FAILED( hr ) )
		return false;
		
	// Obtain an internal feature level index //
	featureLevelIndex = get_feature_level_index( curFeatureLevel );
		
	// Obtain a pointer to the backbuffer //
	ID3D11Texture2D* pBackBuffer;
	hr = pSwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), (LPVOID*)&pBackBuffer );
	if( FAILED( hr ) ) 
		return false;
	
	// Create a render target from the buffer //
	hr = pDevice->CreateRenderTargetView( pBackBuffer, NULL, &pRTV );
	if( FAILED( hr ) )
		return false;

	RELEASE_UNKNOWN( pBackBuffer );

	// Create Depth Stencil surface //
	D3D11_TEXTURE2D_DESC dsDesc;
	dsDesc.Width			= init.displayWidth;
	dsDesc.Height			= init.displayHeight;
	dsDesc.MipLevels		= 1;
	dsDesc.ArraySize		= 1;
	dsDesc.Format			= ( init.stencilBits > 0 ) ? DXGI_FORMAT_D24_UNORM_S8_UINT :	\
							  ( init.depthBits > 24 ) ? DXGI_FORMAT_D32_FLOAT : DXGI_FORMAT_D16_UNORM;
	dsDesc.SampleDesc		= multisampleDesc;
	dsDesc.Usage			= D3D11_USAGE_DEFAULT;
	dsDesc.BindFlags		= D3D11_BIND_DEPTH_STENCIL;
	dsDesc.CPUAccessFlags	= 0;
	dsDesc.MiscFlags		= 0;

	ID3D11Texture2D* pDepthStencil = NULL;
	if( FAILED( pDevice->CreateTexture2D( &dsDesc, NULL, &pDepthStencil ) ) )
		return false;

	D3D11_DEPTH_STENCIL_VIEW_DESC dsViewDesc;
	dsViewDesc.Format			= dsDesc.Format;
	dsViewDesc.ViewDimension	= D3D11_DSV_DIMENSION_TEXTURE2D;
	dsViewDesc.Flags			= 0;
	dsViewDesc.Texture2D.MipSlice = 0;

	if( FAILED( pDevice->CreateDepthStencilView( pDepthStencil, &dsViewDesc, &pDSV ) ) )
		return false;
	
	// Make it current //
	pDeviceContext->OMSetRenderTargets( 1, &pRTV, pDSV );
	
	// Set viewport //
	D3D11_VIEWPORT vp;
	vp.Width		= init.displayWidth;
	vp.Height		= init.displayHeight;
	vp.MinDepth		= 0.0F;
	vp.MaxDepth		= 1.0F;
	vp.TopLeftX		= 0.0F;
	vp.TopLeftY		= 0.0F;
	pDeviceContext->RSSetViewports( 1, &vp );
		   
	return true;									   
}



//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=//
//										INPUT ASSEMBLER AND BUFFER METHODS												  //
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=//

//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// CreateVertexFormat
int	dh_renderer::CreateVertexFormat( const dh_vertex_element* desc, int nElements )
{
	if( !desc || nElements <= 0 )
		return INVALID_HANDLE;

	D3D11_INPUT_ELEMENT_DESC* vDesc = new D3D11_INPUT_ELEMENT_DESC[nElements];
	memset( vDesc, 0, sizeof( D3D11_INPUT_ELEMENT_DESC ) * nElements );

	for( unsigned i = 0; i < nElements; ++i )
	{
		if( !VALID_HANDLE( desc[i].shaderHandle ) )
			return INVALID_HANDLE;

		vDesc[i].SemanticName			= desc[i].semantic.c_str();
		vDesc[i].SemanticIndex			= desc[i].semanticIndex;
		vDesc[i].Format					= (DXGI_FORMAT)desc[i].format;
		vDesc[i].InputSlot				= desc[i].inputSlot;
		vDesc[i].AlignedByteOffset		= desc[i].alignmentBytes;
		vDesc[i].InputSlotClass			= ( desc[i].inputClassType & VERTEX_DATA ) ? D3D11_INPUT_PER_VERTEX_DATA : D3D11_INPUT_PER_INSTANCE_DATA;
		vDesc[i].InstanceDataStepRate	= desc[i].nInstances;
	}

	ID3D11InputLayout* newLayout;
	HRESULT hr;
	
	void* byteCodeBuf;
	unsigned int byteCodeLen;
	
//	byteCodeBuf = vEffects[desc[0].shaderHandle].techniqueMap[desc[0].techniqueName].pPasses[desc[0].techniquePass].passBlob.buf;
//	byteCodeLen = vEffects[desc[0].shaderHandle].techniqueMap[desc[0].techniqueName].pPasses[desc[0].techniquePass].passBlob.bufSize;
	
	hr = pDevice->CreateInputLayout(vDesc, nElements, byteCodeBuf, byteCodeLen, &newLayout);
	if( FAILED( hr ) ) 
	{
		delete[] vDesc;
		return INVALID_HANDLE;
	}

	vVertexFormats.push_back( newLayout );
	return vVertexFormats.size() - 1;
}



//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// CreateVertexBuffer
int dh_renderer::CreateVertexBuffer( void* buf, int bufSize, unsigned char usage )
{
	D3D11_BUFFER_DESC bufDesc;
	memset( &bufDesc, 0, sizeof( D3D11_BUFFER_DESC ) );
	bufDesc.Usage			= ( usage & USAGE_STATIC ) ? D3D11_USAGE_DEFAULT : D3D11_USAGE_DYNAMIC;
	bufDesc.ByteWidth		= bufSize;
	bufDesc.BindFlags		= D3D11_BIND_VERTEX_BUFFER;
	
	D3D11_SUBRESOURCE_DATA bufInit;
	memset( &bufInit, 0, sizeof( D3D11_SUBRESOURCE_DATA ) );
	bufInit.pSysMem			= buf;
	
	ID3D11Buffer* newBuf;
	if( FAILED( pDevice->CreateBuffer( &bufDesc, &bufInit, &newBuf ) ) )
		return INVALID_HANDLE;
	
	int nVertexBufferSlots = vVertexBufferSlots.size();
	if( nVertexBufferSlots > 0 )
	{
		int index = vVertexBufferSlots[nVertexBufferSlots - 1];
		vVertexBuffers[index] = newBuf;
		vVertexBufferSlots.pop_back();
		return index;
	}
	
	else
	{
		vVertexBuffers.push_back( newBuf );
		return vVertexBuffers.size() - 1;
	}
}


//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// CreateIndexBuffer
int dh_renderer::CreateIndexBuffer( void* buf, int nIndices, unsigned char usage )
{
	D3D11_BUFFER_DESC bufDesc;
	memset( &bufDesc, 0, sizeof( D3D11_BUFFER_DESC ) );
	bufDesc.Usage			= ( usage & USAGE_STATIC ) ? D3D11_USAGE_DEFAULT : D3D11_USAGE_DYNAMIC;
	bufDesc.ByteWidth		= sizeof( unsigned int ) * nIndices;
	bufDesc.BindFlags		= D3D11_BIND_INDEX_BUFFER;
	
	D3D11_SUBRESOURCE_DATA bufInit;
	memset( &bufInit, 0, sizeof( D3D11_SUBRESOURCE_DATA ) );
	bufInit.pSysMem			= buf;
	
	ID3D11Buffer* newBuf;
	if( FAILED( pDevice->CreateBuffer( &bufDesc, &bufInit, &newBuf ) ) )
		return INVALID_HANDLE;
		
	int nIndexBufferSlots = vIndexBufferSlots.size();
	if( nIndexBufferSlots > 0 )
	{
		int index = vIndexBufferSlots[nIndexBufferSlots - 1];
		vIndexBuffers[index] = newBuf;
		vIndexBufferSlots.pop_back();
		return index;
	}
	
	else
	{
		vIndexBuffers.push_back( newBuf );
		return vIndexBuffers.size() - 1;
	}
}


//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// BindVertexBuffers
bool dh_renderer::BindVertexBuffers( int* buffers, unsigned int* bufferSizes, unsigned int* bufferOffsets, unsigned int nBuffers )
{
	if( !buffers || !bufferSizes || !bufferOffsets || ( nBuffers <= 0 ) )
		return false;
	
	ID3D11Buffer* d3dBuffers[MAX_VERTEX_STREAMS];
	memset( d3dBuffers, 0, sizeof( ID3D11Buffer* ) * MAX_VERTEX_STREAMS );
	int nLocalBuffers = vVertexBuffers.size();
	for( unsigned int i = 0; i < nBuffers; ++i )
	{
		if( buffers[i] > ( nLocalBuffers - 1 )  || !VALID_BUFFER( buffers[i] ) )
			continue;
		
		d3dBuffers[i] = vVertexBuffers[buffers[i]];
	}
	
	pDeviceContext->IASetVertexBuffers( 0, nBuffers, d3dBuffers, bufferSizes, bufferOffsets ) ;
	
	return true;
}


//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// BindIndexBuffer
bool dh_renderer::BindIndexBuffer( int buffer, unsigned int offset )
{
	if( !VALID_BUFFER( buffer ) ) 
		return false;
	
	pDeviceContext->IASetIndexBuffer( vIndexBuffers[buffer], DXGI_FORMAT_R32_UINT, offset ); 
	
	return true;
}


//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// DeleteVertexBuffer
void dh_renderer::DeleteVertexBuffer( const int handle )
{
	if( handle < vVertexBuffers.size() )
	{
		RELEASE_UNKNOWN( vVertexBuffers[handle] );
		vVertexBufferSlots.push_back( handle );
	}
}



//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// DeleteIndexBuffer
void dh_renderer::DeleteIndexBuffer( const int handle )
{
	if( handle < vIndexBuffers.size() )
	{
		RELEASE_UNKNOWN( vIndexBuffers[handle] );
		vIndexBufferSlots.push_back( handle );
	}
}



//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=//
//									SHADER METHODS													  //
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=//
int	dh_renderer::CreateVertexShader(char* shaderText)
{
	HRESULT hr;
	ID3DBlob* pShaderBlob, *pErrorBlob;
	ID3D11VertexShader* vs;
	void* pByteCode;
	char* shaderCode;
	SIZE_T byteCodeLen;
	


	hr = D3DCompile(shaderText, strlen(shaderText), NULL, NULL, NULL, "vs_depth", "vs_5_0", 0, 0, &pShaderBlob, &pErrorBlob);
	if(FAILED(hr))
	{
		if(pErrorBlob)
		{
			char* errorTxt = (char*)pErrorBlob->GetBufferPointer();
			MessageBox(NULL, errorTxt, "Shader Compilation Error", MB_OK | MB_ICONEXCLAMATION);
		}
		return -1;
	}

	pByteCode = pShaderBlob->GetBufferPointer();
	byteCodeLen = pShaderBlob->GetBufferSize();

	hr = pDevice->CreateVertexShader(pByteCode, byteCodeLen, NULL, &vs);
	if(FAILED(hr))
		return -1;

	vVertexShaders.push_back(vs);
	return vVertexShaders.size() - 1;
}



//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=//
//								SCREEN AND SCENE PRESENTATION METHODS							      //
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=//


//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// Clear
void dh_renderer::ClearBackBuffer( const unsigned char clearFlags, float r, float g, float b )
{
	float clearColor[4] = { r, g, b, 1.0F };
	pDeviceContext->ClearRenderTargetView( pRTV, clearColor );
}


//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// ClearDepthStencilBuffer
void dh_renderer::ClearDepthStencilBuffer( unsigned char buf, float depthClear, unsigned char stencilClear )
{
	unsigned int clearFlags = 0;
	if( buf & CLEAR_ZBUFFER ) clearFlags |= D3D11_CLEAR_DEPTH;
	if( buf & CLEAR_STENCILBUFFER ) clearFlags |= D3D11_CLEAR_STENCIL;

	pDeviceContext->ClearDepthStencilView( pDSV, clearFlags, depthClear, stencilClear );
}



//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// PresentScene
void dh_renderer::PresentScene()
{
	pSwapChain->Present( 0, 0 );
}